using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;

namespace Framework.Network
{
    public interface ISerializer
    {
        T Deserialize<T>(byte[] data, int index, int count);
        byte[] Serialize<TMsg>(TMsg msg);
    }
    
    public enum SocketMessageType
    {
        Connect,
        Message,
        Close,
    }

    public enum SocketProtocolType
    {
        Tcp,
        TcpMoon,
    }

    public class LogicException : ApplicationException
    {
        public LogicException(string message) : base(message)
        {
            
        }
    }
    
    public class NetSocket
    {
        private Dictionary<int, BaseConnection> connections = new Dictionary<int, BaseConnection>();
        private ConcurrentQueue<SocketMessage> messageQueue = new ConcurrentQueue<SocketMessage>();
        
        private Timer tmr = null;

        private BaseConnection CreateConnection(SocketProtocolType protocolType)
        {
            switch (protocolType)
            {
                case SocketProtocolType.TcpMoon:
                    return new MoonConnection();
                case SocketProtocolType.Tcp:
                    return new StreamConnection();
                default:
                    return null;
            }
        }

        public SocketMessage Connect(int connectId, string host, int port, SocketProtocolType protocolType)
        {
            try
            {
                BaseConnection connection = CreateConnection(protocolType);
                connection.HandleMessage = PushMessage;
                connection.Socket.Connect(host, port);
                connection.ConnectId = connectId;
                connections.Add(connection.ConnectId, connection);
                connection.Start();

                return new SocketMessage(connection.ConnectId, SocketMessageType.Connect, (NetBuffer)null, 0);
            }
            catch (Exception e)
            {
                return new SocketMessage(0, SocketMessageType.Connect, NetUtil.GetErrorMessage(e), 0);
            }
        }

        public void ConnectAsync(int connectId, string host, int port, SocketProtocolType protocolType, int timeout = 0)
        {
            try
            {
                BaseConnection connection = CreateConnection(protocolType);
                connection.HandleMessage = PushMessage;
                connection.ConnectId = connectId;

                Socket socket = connection.Socket;
                
                if (timeout > 0)
                {
                    tmr = new Timer((obj) =>
                    {
                        BaseConnection conn = (BaseConnection)obj;
                        if (!conn.IsConnected)
                        {
                            conn.Close();
                            PushMessage(new (0, SocketMessageType.Connect, NetUtil.GetErrorMessage(new ApplicationException("timeout")), 0));
                        }
                    }, connection, timeout, 0);
                }

                socket.BeginConnect(host, port, OnConnect, connection);
            }
            catch (Exception e)
            {
                PushMessage(new (0, SocketMessageType.Connect, NetUtil.GetErrorMessage(e), 0));
            }
        }
        
        // 创建连接回调
        private void OnConnect(IAsyncResult ar)
        {
            try
            {
                BaseConnection conn = ar.AsyncState as BaseConnection;
                Socket socket = conn.Socket;
                if (socket != null)
                {
                    socket.EndConnect(ar);

                    if (tmr != null)
                        tmr.Dispose();
                    
                    connections.Add(conn.ConnectId, conn);
                    conn.Start();

                    NetBuffer buffer = new NetBuffer();
                    buffer.Write(socket.RemoteEndPoint.ToString());
                    PushMessage(new (conn.ConnectId, SocketMessageType.Connect, buffer, 0));
                }
            }
            catch (Exception e)
            {
                PushMessage(new (0, SocketMessageType.Connect, NetUtil.GetErrorMessage(e), 0));
            }
        }

        public void Send(int connectId, byte[] data)
        {
            NetBuffer buffer = new NetBuffer();
            buffer.Write(data, 0, data.Length);
            Send(connectId, buffer);
        }

        public bool Send(int connectId, NetBuffer data)
        {
            if (connections.TryGetValue(connectId, out BaseConnection connection))
            {
                connection.Send(data);
                return true;
            }

            return false;
        }

        public bool Read(int connectId, bool line, int count, int sessionId)
        {
            if (connections.TryGetValue(connectId, out BaseConnection connection))
            {
                connection.Read(line, count, sessionId);
                return true;
            }

            return false;
        }

        public void Close(int connectId)
        {
            if (tmr != null)
                tmr.Dispose();
            
            if (connections.TryGetValue(connectId, out BaseConnection connection))
            {
                connection.Close();
                connections.Remove(connectId);
            }
        }

        public void CloseAll()
        {
            foreach (KeyValuePair<int, BaseConnection> connection in connections)
            {
                connection.Value.Close();
            }
        }

        public SocketMessage PopMessage()
        {
            if (messageQueue.TryDequeue(out SocketMessage message))
            {
                return message;
            }

            return null;
        }

        private void PushMessage(SocketMessage message)
        {
            if (message.MessageType == SocketMessageType.Close && message.ConnectId > 0)
            {
                Close(message.ConnectId);
            }
            messageQueue.Enqueue(message);
        }
    }
}